<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Ok5y6" id="Ok5y6"><span data-lake-id="u8f10e449" id="u8f10e449">典型回答</span></h1>
  <p data-lake-id="u46f665cd" id="u46f665cd"><br></p>
  <p data-lake-id="u9205c8ae" id="u9205c8ae"><span data-lake-id="ua6ab2cba" id="ua6ab2cba">其实，在阿里电商的秒杀等（据我了解，淘宝、天猫、猫超、大麦等都是这么干的）场景中，主要还是基于MySQL数据库在做扣减的，主要是因为这样做最可靠了（避免了redis扣减方案中的数据不一致、少卖等问题）。</span></p>
  <p data-lake-id="u16f7ee44" id="u16f7ee44"><span data-lake-id="u9b98dfc1" id="u9b98dfc1">​</span><br></p>
  <p data-lake-id="ud16f220c" id="ud16f220c"><span data-lake-id="u0b715807" id="u0b715807">但是我们都知道，数据库是抗不了热点行的并发更新的，于是阿里内部就对MySQL做了patch。</span></p>
  <p data-lake-id="u7d31aae4" id="u7d31aae4"><span data-lake-id="u04c90545" id="u04c90545">​</span><br></p>
  <p data-lake-id="u277109e7" id="u277109e7"><span data-lake-id="ub4822171" id="ub4822171">这个方案其实云上数据库RDS也支持了，所以我就可以讲了。（没开放的技术确实不敢讲，怕被请喝茶。。。）</span></p>
  <p data-lake-id="u9da70db4" id="u9da70db4"><span data-lake-id="udad443dc" id="udad443dc">​</span><br></p>
  <p data-lake-id="u0f377e7d" id="u0f377e7d"><span data-lake-id="u65ac2c81" id="u65ac2c81">这个技术叫做</span><span data-lake-id="u6acfa903" id="u6acfa903" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">Inventory Hint，其实就是一个补丁。（官方介绍：</span><a href="https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/inventory-hint?spm=a2c4g.11186623.0.0.4b224ac9RdTjAG" target="_blank" data-lake-id="u2f29d52b" id="u2f29d52b"><span data-lake-id="ued627fab" id="ued627fab" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/inventory-hint</span></a><span data-lake-id="u12541c80" id="u12541c80" class="lake-fontsize-11" style="color: rgb(24, 24, 24)"> ）</span></p>
  <p data-lake-id="u81d3ff35" id="u81d3ff35"><span data-lake-id="u557133cb" id="u557133cb" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">​</span><br></p>
  <p data-lake-id="u2383ac9f" id="u2383ac9f"><span data-lake-id="u9d7a9ead" id="u9d7a9ead">PS：这里主要是介绍通过Inventory Hint来提升热点更新的并发，但是并不意味着内容只用这个方案来抗热点并发，但是这个是基础，先把这个讲清楚。后续其他的方案，我也会挑一些能讲的介绍。</span></p>
  <p data-lake-id="ufdecfb7d" id="ufdecfb7d"><span data-lake-id="u878d9c10" id="u878d9c10" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">​</span><br></p>
  <h3 data-lake-id="fLSjn" id="fLSjn"><span data-lake-id="u813c4711" id="u813c4711" style="color: rgb(24, 24, 24)">使用方法</span></h3>
  <p data-lake-id="uae74ed4a" id="uae74ed4a"><br></p>
  <p data-lake-id="u6803a2f8" id="u6803a2f8"><span data-lake-id="u56adc6e2" id="u56adc6e2" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">他的用法很简单，只需要在正常的update语句中增加上特殊的hint语句就行了，如：</span></p>
  <p data-lake-id="u270a33e5" id="u270a33e5"><span data-lake-id="u6c466d5f" id="u6c466d5f" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">​</span><br></p>
  <pre lang="java"><code>
UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1)*/ T
SET c = c - 1
WHERE id = 1;
</code></pre>
  <p data-lake-id="uc8281635" id="uc8281635"><br></p>
  <p data-lake-id="uc0f8e19b" id="uc0f8e19b"><span data-lake-id="ueb0015b3" id="ueb0015b3" class="lake-fontsize-11">这里面的</span><code data-lake-id="u61beca24" id="u61beca24"><span data-lake-id="u4e92e1ef" id="u4e92e1ef" class="lake-fontsize-11">COMMIT_ON_SUCCESS</span></code><span data-lake-id="u7f5a7cc4" id="u7f5a7cc4" class="lake-fontsize-11">、</span><code data-lake-id="u399a6b0b" id="u399a6b0b"><span data-lake-id="u6c408633" id="u6c408633" class="lake-fontsize-11">ROLLBACK_ON_FAIL</span></code><span data-lake-id="uc22548cd" id="uc22548cd" class="lake-fontsize-11">和</span><code data-lake-id="u2b69887f" id="u2b69887f"><span data-lake-id="u229fb2a1" id="u229fb2a1" class="lake-fontsize-11">TARGET_AFFECT_ROW</span></code><span data-lake-id="u5d3195c3" id="u5d3195c3" class="lake-fontsize-11">都是一些</span><span data-lake-id="u8347623d" id="u8347623d" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">Hint语法：</span></p>
  <p data-lake-id="uad4cad4d" id="uad4cad4d"><br></p>
  <ul list="uf111a1c2">
   <li fid="u27c5273c" data-lake-id="u1396aa36" id="u1396aa36" style="text-align: left"><span data-lake-id="ud4e519b1" id="ud4e519b1" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">COMMIT_ON_SUCCESS：当前语句执行成功就提交事务上下文。</span></li>
   <li fid="u27c5273c" data-lake-id="uabde7c75" id="uabde7c75" style="text-align: left"><span data-lake-id="u5972b829" id="u5972b829" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">ROLLBACK_ON_FAIL：当前语句执行失败就回滚事务上下文。</span></li>
   <li fid="u27c5273c" data-lake-id="ub155e9b9" id="ub155e9b9" style="text-align: left"><span data-lake-id="u1a82f355" id="u1a82f355" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">TARGET_AFFECT_ROW(NUMBER)：如果当前语句影响行数是指定的就成功，否则语句失败。</span></li>
  </ul>
  <p data-lake-id="u20fbba18" id="u20fbba18" style="text-align: left"><span data-lake-id="u2e820537" id="u2e820537" class="lake-fontsize-11" style="color: rgb(24, 24, 24)">​</span><br></p>
  <blockquote data-lake-id="ua9941ac4" id="ua9941ac4">
   <p data-lake-id="u20c5df56" id="u20c5df56"><span data-lake-id="u52af9071" id="u52af9071">hint：MySQL 中的 "Hint" 是一种特殊的语法，允许开发者向数据库引擎提供如何执行特定查询的额外信息或建议。这些提示不改变查询的结果，但可以影响查询的执行路径，比如如何选择索引、是否使用缓存等。使用 Hint 的目的是为了优化查询性能。</span></p>
  </blockquote>
  <p data-lake-id="u938a6144" id="u938a6144"><span data-lake-id="uc4da2ea6" id="uc4da2ea6">​</span><br></p>
  <p data-lake-id="u1ad20839" id="u1ad20839"><span data-lake-id="ufefb3381" id="ufefb3381">很显然，前面我们提到的这几个hint是阿里自己支持的。所以只有内部的数据库， 或者阿里云的RDS才支持。</span></p>
  <p data-lake-id="uf87c17d2" id="uf87c17d2"><span data-lake-id="u296be95c" id="u296be95c">​</span><br></p>
  <h3 data-lake-id="rQSeO" id="rQSeO"><span data-lake-id="u086afcdc" id="u086afcdc" style="color: rgb(24, 24, 24)">原理介绍</span></h3>
  <p data-lake-id="u39adff2c" id="u39adff2c"><br></p>
  <p data-lake-id="ufc109932" id="ufc109932"><span data-lake-id="u9b7710c1" id="u9b7710c1">当我们是使用</span><code data-lake-id="ud47bf54e" id="ud47bf54e"><span data-lake-id="u3562f281" id="u3562f281">COMMIT_ON_SUCCESS</span></code><span data-lake-id="ue0b900a7" id="ue0b900a7">等hint标记了一条SQL之后，就相当于告诉MySQL内核，这行可能是热点更新。</span></p>
  <p data-lake-id="uc613ad14" id="uc613ad14"><span data-lake-id="ub5848516" id="ub5848516">​</span><br></p>
  <p data-lake-id="ufabe6c7b" id="ufabe6c7b"><span data-lake-id="u5164cc2f" id="u5164cc2f">于是，MySQL的内核层就会自动识别带此类标记的更新操作，在一定的时间间隔内，将收集到的更新操作按照主键或者唯一键进行分组，这样更新相同行的操作就会被分到同一组中。</span></p>
  <p data-lake-id="u8185217f" id="u8185217f"><span data-lake-id="u099853fd" id="u099853fd">​</span><br></p>
  <p data-lake-id="u928f5ba0" id="u928f5ba0"><img src="https://cdn.nlark.com/yuque/0/2024/png/5378072/1705316172477-39e212c9-0317-4110-8032-bd18d6002b32.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_32%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u9633f932" id="u9633f932"><br></p>
  <blockquote data-lake-id="u8bc28bfb" id="u8bc28bfb">
   <p data-lake-id="u88c7ec6c" id="u88c7ec6c"><span data-lake-id="ubc2d1620" id="ubc2d1620">为了进一步提升性能，在实现上，使用两个执行单元。当第一个执行单元收集完毕准备提交时，第二个执行单元立即开始收集更新操作；当第二个执行单元收集完毕准备提交时，第一个执行单元已经提交完毕并开始收集新一批的更新操作，两个单元不断切换，并行执行。</span></p>
  </blockquote>
  <p data-lake-id="ub759d9b8" id="ub759d9b8"><span data-lake-id="ue86acf11" id="ue86acf11">​</span><br></p>
  <p data-lake-id="u1c1fdd53" id="u1c1fdd53"><span data-lake-id="uaf9f207e" id="uaf9f207e">根据热点行做了分组之后，就可以作进一步优化了，这个过程主要有3个关键的优化点：</span></p>
  <p data-lake-id="uc5b105a0" id="uc5b105a0"><span data-lake-id="ue0e0f719" id="ue0e0f719">​</span><br></p>
  <p data-lake-id="u4a77a59b" id="u4a77a59b"><strong><span data-lake-id="uea6c45f0" id="uea6c45f0">1、减少行级锁的申请等待</span></strong></p>
  <p data-lake-id="u3e620f1d" id="u3e620f1d"><span data-lake-id="ua87ac98c" id="ua87ac98c">​</span><br></p>
  <p data-lake-id="uec1fa8ea" id="uec1fa8ea"><span data-lake-id="u51ba260c" id="u51ba260c">在同一组中，需要更新的都是同一条记录，那么根据SQL的提交顺序，就可以排队了。</span></p>
  <p data-lake-id="ud0b10fe2" id="ud0b10fe2"><span data-lake-id="ua2f3a32b" id="ua2f3a32b">​</span><br></p>
  <p data-lake-id="u0d28f0a7" id="u0d28f0a7"><span data-lake-id="u5fe1960c" id="u5fe1960c">然后我们只需要在第一条更新SQL（Leader)执行的时候，尝试去获取目标行的锁，如果获取成功，则开始操作。</span></p>
  <p data-lake-id="u7f04ace9" id="u7f04ace9"><span data-lake-id="ub7cb64fa" id="ub7cb64fa">​</span><br></p>
  <p data-lake-id="u8d4a6f39" id="u8d4a6f39"><span data-lake-id="u11482a33" id="u11482a33">然后这一组中后续的更新操作（Follower）也会尝试获取锁，但是会先判断是不是已经被第一条更新操作获取到了，如果是的话，那么就不需要等待，直接获取锁。</span></p>
  <p data-lake-id="ubf4983a0" id="ubf4983a0"><span data-lake-id="ucbbff648" id="ucbbff648">​</span><br></p>
  <p data-lake-id="u2fb9e2ae" id="u2fb9e2ae"><span data-lake-id="u73ddf99e" id="u73ddf99e">这样就可以大大降低行级锁的申请的阻塞等待时长。</span></p>
  <p data-lake-id="uf9fd20ae" id="uf9fd20ae"><span data-lake-id="ua9b3ff38" id="ua9b3ff38">​</span><br></p>
  <p data-lake-id="ued8a2a24" id="ued8a2a24"><strong><span data-lake-id="uc0c8e39c" id="uc0c8e39c">2、减少B+树的索引遍历操作</span></strong></p>
  <p data-lake-id="u02f507ba" id="u02f507ba"><span data-lake-id="u3d92cfb1" id="u3d92cfb1">​</span><br></p>
  <p data-lake-id="uae7aa037" id="uae7aa037"><span data-lake-id="u95134acb" id="u95134acb">MySQL是以B+索引的方式管理数据的，每次执行查询时，都需要遍历索引才能定位到目标数据行，数据表越大，索引层级越多，遍历时间就越长。</span></p>
  <p data-lake-id="ud741dfc5" id="ud741dfc5"><span data-lake-id="u0c4ed7e0" id="u0c4ed7e0">​</span><br></p>
  <p data-lake-id="u4594db3c" id="u4594db3c"><span data-lake-id="u5ae5b3b0" id="u5ae5b3b0">如果针对热点行更新操作做了分组之后，我们只需要在每组的第一条SQL执行过程中，通过遍历索引定位数据行，之后就可以把这些数据行缓存到Row Cache中，并且在Row Cache进行修改。</span></p>
  <p data-lake-id="ub9b7573c" id="ub9b7573c"><span data-lake-id="udfc27aa5" id="udfc27aa5">​</span><br></p>
  <p data-lake-id="ucf69f920" id="ucf69f920"><span data-lake-id="u3f915767" id="u3f915767">在同组的后续操作时，也不再需要进行数据索引了，直接从Row Cache获取数据并修改就行了。</span></p>
  <p data-lake-id="ud71abc10" id="ud71abc10"><span data-lake-id="uce22c0cf" id="uce22c0cf">​</span><br></p>
  <p data-lake-id="u4ecafc4b" id="u4ecafc4b"><span data-lake-id="u7f19951c" id="u7f19951c">这样就大大降低了B+树的索引遍历操作的耗时。</span></p>
  <p data-lake-id="ub93b1928" id="ub93b1928"><br></p>
  <p data-lake-id="u768eb148" id="u768eb148"><strong><span data-lake-id="u639ef313" id="u639ef313">3、减少事务提交次数</span></strong></p>
  <p data-lake-id="u92df713a" id="u92df713a"><strong><span data-lake-id="u26b57008" id="u26b57008">​</span></strong><br></p>
  <p data-lake-id="u8d7bfb78" id="u8d7bfb78"><span data-lake-id="u0507ad80" id="u0507ad80">如果是没有用这种方式，我们的多条update语句会是多条事务，那么每一个事务都要单独做一次提交。</span></p>
  <p data-lake-id="u15d67263" id="u15d67263"><span data-lake-id="ua58984e7" id="ua58984e7">​</span><br></p>
  <p data-lake-id="u1555ed9a" id="u1555ed9a"><span data-lake-id="u424a2e89" id="u424a2e89">有了分组、排队、组提交之后，就只需要一组中的并发操作都执行完，然后做一次组提交即可，大大降低提交次数。</span></p>
  <p data-lake-id="u75e90c74" id="u75e90c74"><span data-lake-id="ua77c95d7" id="ua77c95d7">​</span><br></p>
  <h1 data-lake-id="GCJpw" id="GCJpw"><span data-lake-id="ubeeec59e" id="ubeeec59e">扩展知识</span></h1>
  <p data-lake-id="uf5c1ad71" id="uf5c1ad71"><br></p>
  <h2 data-lake-id="fdRvO" id="fdRvO"><span data-lake-id="uf52ac99a" id="uf52ac99a">statement queue</span></h2>
  <p data-lake-id="ub9fe6145" id="ub9fe6145"><br></p>
  <p data-lake-id="ua1d98a0e" id="ua1d98a0e"><span data-lake-id="u9d60de04" id="u9d60de04">除了inventory hint，还有一个技术叫做</span><span data-lake-id="u2058c5c1" id="u2058c5c1" style="color: rgb(24, 24, 24)">Statement Queue（</span><a href="https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/statement-queue?spm=a2c4g.11186623.0.0.3af84f5e9EHaQp" target="_blank" data-lake-id="u4fff45b7" id="u4fff45b7"><span data-lake-id="u407a28d8" id="u407a28d8" style="color: rgb(24, 24, 24)">https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/statement-queue</span></a><span data-lake-id="u8fcb0899" id="u8fcb0899" style="color: rgb(24, 24, 24)"> ），也</span><span data-lake-id="u832c379f" id="u832c379f">是一种减少冲突开销，有效提高实例性能的手段。</span></p>
  <p data-lake-id="uae77eb92" id="uae77eb92"><span data-lake-id="u4594a2d9" id="u4594a2d9">​</span><br></p>
  <p data-lake-id="u3cf5d3f7" id="u3cf5d3f7"><span data-lake-id="ua56a4183" id="ua56a4183">通过statement queue，可以配置桶的数量和一个桶中允许的并发数。同时提供了两个接口便于查询Statement Queue状态，方便查看并发情况。详细的就不展开了（大家知道有这么个东西就行），在上面的链接文档中可以查看使用方式。</span></p>
  <p data-lake-id="u07e8c928" id="u07e8c928"><span data-lake-id="u36e67c4b" id="u36e67c4b">​</span><br></p>
  <p data-lake-id="u213222a8" id="u213222a8"><span data-lake-id="u57811b7a" id="u57811b7a">​</span><br></p>
 </body>
</html>